package net.pulseproject.router.modules.logic.beatstabilizer;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Timer;
import java.util.TimerTask;

import javax.sound.midi.ShortMessage;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import net.pulseproject.commons.midi.Receivable;
import net.pulseproject.commons.midi.Transmitable;
import net.pulseproject.commons.midi.entity.ControllerMessage;
import net.pulseproject.commons.misc.TimerTaskWrapperX;
import net.pulseproject.commons.misc.TimerTaskWrapperX.TimerListener;
import net.pulseproject.router.modules.useable.BeatStabilizerModule;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

@Deprecated // move to debugger-app
public class BeatStabilizerDebuggerGUiApp implements TimerListener {
	
	private static final Log LOG = LogFactory.getLog(BeatStabilizerDebuggerGUiApp.class);
	private static final String LBL_BTN_METRONOM_START = "Metronom Start";
	private static final String LBL_BTN_METRONOM_STOP = "Metronom Stop";

	private BeatStabilizerModule module;
	
	private transient long recentReceiveTime = -1;
	private final Collection<Receivable> outgoingModules = new ArrayList<Receivable>(Arrays.asList(new Receivable() {
		@Override public void receive(ShortMessage message, Transmitable transmitable) {
			LOG.info("APPPP received midi message");
			final long newMs = System.currentTimeMillis();
			final long ms;
			if(recentReceiveTime == -1) {
				ms = 0;
			} else {
				ms = newMs - recentReceiveTime;
			}
			lblReceiveDelay.setText(ms + "ms");
			recentReceiveTime = newMs;
	}}));
	
	private final JButton btnStartStopMetronom = new JButton(LBL_BTN_METRONOM_START);
	private final JSlider slider = new JSlider();
	private final JButton btnInit = new JButton("Init Module");
	private final JButton btnDestroy = new JButton("Destroy");
	private final JButton btnTap = new JButton("tap");
	private final JButton btnSusFire = new JButton("Sustained Fire");
	private final JCheckBox chkSuppressMetronom = new JCheckBox("Suppress");
	private final JPanel pnlBlinkSent = new JPanel();
	private final JPanel pnlBlinkRawReceive = new JPanel();
	private final JTextField configGlobalMinMs = new JTextField("333"/* ~= 180bpm */); // 200ms = 300bpm
	private final JTextField configGlobalMaxMs = new JTextField("1000"/* = 60bpm*/); // 2000ms = 30bpm
	private final JTextField configFrameDeviation = new JTextField("5.0");
	private final JLabel lblCurrentMetronomValue = new JLabel();
	private final JLabel lblReceiveDelay = new JLabel("0ms");
	private final JLabel lblNextFrameWindow = new JLabel("[XX-XX]");
	
	private transient Timer timer;
	private transient int timerDelay = 1000; // FIXME initialize properly
	private transient int inpGlobalMinMs;
	private transient int inpGlobalMaxMs;
	private transient double inpFrameDeviation;
	
	public static BeatStabilizerDebuggerGUiApp DEBUG;
	
	
	public static void main(String[] args) throws Exception {
		LOG.info("main(args)");
		new BeatStabilizerDebuggerGUiApp().run();
	}

	public static void debugBlinkSet(boolean isArtificialMessage) {
		if(DEBUG != null) {
			DEBUG.blinkSent(isArtificialMessage);
		}
	}
	public static void debugBlinkRawReceive() {
		if(DEBUG != null) {
			DEBUG.blinkRawReceive();
		}
	}

	public static void debugDisplayNextGreenFrameWindow(String string) {
		if(DEBUG != null) {
			DEBUG.displayNextGreenFrameWindow(string);
		}
	}
	
	void doInit() {
		LOG.debug("doInit()");
		this.btnInit.setEnabled(false);
		this.btnDestroy.setEnabled(true);
		this.btnTap.setEnabled(true);
		this.btnSusFire.setEnabled(true);
		this.btnStartStopMetronom.setEnabled(true);
		
		this.updateInputValues();
		
		final int sustainedFireWindow = 20;
		this.module = new BeatStabilizerModule("someModuleId",
			this.outgoingModules, new BeatStabilizerConfig(this.inpGlobalMinMs, this.inpGlobalMaxMs, this.inpFrameDeviation), sustainedFireWindow);
	}
	
	void doDestroy() {
		LOG.debug("doDestroy()");
		if(this.module != null) {
			this.module.destroy();
			this.module = null;
		}
		this.stopTimer();
		this.btnStartStopMetronom.setText(LBL_BTN_METRONOM_START);
		this.btnInit.setEnabled(true);
		this.btnDestroy.setEnabled(false);
		this.btnTap.setEnabled(false);
		this.btnSusFire.setEnabled(false);
		this.btnStartStopMetronom.setEnabled(false);
	}
	
	private void updateInputValues() {
		this.inpGlobalMinMs = Integer.parseInt(this.configGlobalMinMs.getText());
		this.inpGlobalMaxMs = Integer.parseInt(this.configGlobalMaxMs.getText());
		this.inpFrameDeviation = Double.parseDouble(this.configFrameDeviation.getText());
	}
	
	public void run() {
		this.showFrame();
		DEBUG = this;
	}
	private void initBlink(JPanel panel) {
		Dimension pnlBlinkSize = new Dimension(10, 10);
		panel.setPreferredSize(pnlBlinkSize);
		panel.setSize(pnlBlinkSize);
		panel.setOpaque(true);
		panel.setBackground(Color.WHITE);
	}
	
	private void showFrame() {
		this.btnInit.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) {
			doInit();
		}});
		this.btnDestroy.setEnabled(false);
		this.btnDestroy.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) {
			doDestroy();
		}});
		
		Dimension btnTapSize = new Dimension(200, 80);
		this.btnTap.setSize(btnTapSize);
		this.btnTap.setPreferredSize(btnTapSize);
		this.btnTap.setEnabled(false);
		this.btnTap.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) {
			doTap();
		}});
		
		this.btnStartStopMetronom.setEnabled(false);
		this.btnStartStopMetronom.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) {
			doStartStopMetronom();
		}});
		
		this.btnSusFire.setEnabled(false);
		this.btnSusFire.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) {
			doSusFire();
		}});

//		this.slider.setSnapToTicks(true);
		this.slider.setMinimum(100); // approx 500bpm
		this.slider.setMaximum(3000); // approx 20bpm
		this.slider.setValue(1000);
		this.slider.setPaintTicks(true);
//		slider.setOrientation(SwingConstants.VERTICAL);
		this.slider.setInverted(true);
		this.slider.setMajorTickSpacing(900);
		
		this.doChangeMetronomSpeed(this.slider.getValue()); // init value
		
		this.slider.addChangeListener(new ChangeListener() {
			@Override public void stateChanged(ChangeEvent e) {
				doChangeMetronomSpeed(slider.getValue());
			}
		});
		
		this.initBlink(this.pnlBlinkRawReceive);
		this.initBlink(this.pnlBlinkSent);
		
		JPanel northPanel = new JPanel();
		northPanel.add(new JLabel("Min MS:"));
		northPanel.add(this.configGlobalMinMs);
		northPanel.add(new JLabel("Max MS:"));
		northPanel.add(this.configGlobalMaxMs);
		northPanel.add(new JLabel("Frame Dev.:"));
		northPanel.add(this.configFrameDeviation);
		northPanel.add(this.btnInit);
		northPanel.add(this.btnDestroy);
		
		JPanel metronomPanel = new JPanel(new BorderLayout());
		
		JPanel pnlCmd = new JPanel();
		pnlCmd.add(this.btnSusFire);
		pnlCmd.add(this.btnStartStopMetronom);
		pnlCmd.add(this.chkSuppressMetronom);
		metronomPanel.add(pnlCmd, BorderLayout.NORTH);
		
		metronomPanel.add(this.lblCurrentMetronomValue, BorderLayout.WEST);
		metronomPanel.add(this.slider, BorderLayout.CENTER);
		
		JPanel southPanel = new JPanel(new BorderLayout());
		southPanel.add(metronomPanel, BorderLayout.CENTER);
		southPanel.add(this.btnTap, BorderLayout.SOUTH);
		
		JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		
		statusPanel.add(new JLabel("  Frame Window: "));
		statusPanel.add(this.lblNextFrameWindow);
		
		statusPanel.add(new JLabel("  Delay: "));
		statusPanel.add(this.lblReceiveDelay);
		
		statusPanel.add(new JLabel("  Sent: "));
		statusPanel.add(this.pnlBlinkSent);
		
		statusPanel.add(new JLabel("  Raw Receive: "));
		statusPanel.add(this.pnlBlinkRawReceive);
		
		JPanel panel = new JPanel(new BorderLayout());
		panel.add(northPanel, BorderLayout.NORTH);
		panel.add(southPanel, BorderLayout.CENTER);
		panel.add(statusPanel, BorderLayout.SOUTH);
		JFrame frame = new JFrame();
		frame.setTitle("Beat Stabilizer Debugger");
		frame.getContentPane().add(panel);
		frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		frame.pack();
		frame.setVisible(true);
	}
	
	void doChangeMetronomSpeed(int newSpeed) {
		this.timerDelay = newSpeed;
		this.lblCurrentMetronomValue.setText(newSpeed + "ms");
	}
	
	void doStartStopMetronom() {
		final String currentLbl = this.btnStartStopMetronom.getText();
		LOG.info("doStartStopMetronom() ... currentLbl=" + currentLbl);
		
		this.btnStartStopMetronom.setText(currentLbl.equals(LBL_BTN_METRONOM_START) ? LBL_BTN_METRONOM_STOP : LBL_BTN_METRONOM_START);
		
		if(currentLbl.equals(LBL_BTN_METRONOM_START)) {
			this.startMetronomTimer();
			
		} else { // STOP
			this.stopTimer();
		}
	}
	
	@Override public void /*Metronom*/onTimerComplete() {
		if(this.chkSuppressMetronom.isSelected() == false) {
			LOG.debug("metronom sending message");
			this.sendMidiMessage();
		} else {
			LOG.debug("Not sending MIDI as suppressed!");
		}
		
		this.stopTimer();
		this.startMetronomTimer();
	}
	
	private void stopTimer() {
		if(this.timer != null) {
			this.timer.cancel();
			this.timer = null;
		}
	}
	
	private void startMetronomTimer() {
//		LOG.debug("startMetronomTimer() ... this.timerDelay=" + this.timerDelay);
		
		this.timer = new Timer();
		this.timer.schedule(new TimerTaskWrapperX(this), this.timerDelay);
	}

	void doTap() {
		LOG.info("doTap()");
		this.sendMidiMessage();
	}
	void doSusFire() {
		LOG.info("doSusFire()");
		new Thread(new Runnable() {
			@Override
			public void run() {
				onRunSustainedFireThread();
			}
		}, "SusFireThread").start();
	}
	
	void onRunSustainedFireThread() {
		LOG.trace("onRunSustainedFireThread()");
		// to see how such a sustained fire looks like, see:
		// http://code.google.com/p/pulse-music/issues/detail?id=12#c4
		try {
			this.sendMidiMessage();
			Thread.sleep(2);
			this.sendMidiMessage();
		} catch(Exception e) {
			LOG.error("doSusFire() sustain died!", e);
		}
	}
	
	private void sendMidiMessage() {
		this.module.receive(new ControllerMessage(1, 1, 50).build(), null/*module does not need reference of passing source*/);
	}


	public void blinkSent(boolean isArtificialMessage) {
		this.pnlBlinkSent.setBackground(isArtificialMessage ? Color.RED : Color.GREEN);
		new Timer().schedule(new TimerTask() { @Override public void run() {
			pnlBlinkSent.setBackground(Color.WHITE);
		} }, 80);
	}


	public void blinkRawReceive() {
		this.pnlBlinkRawReceive.setBackground(Color.BLUE);
		new Timer().schedule(new TimerTask() { @Override public void run() {
			pnlBlinkRawReceive.setBackground(Color.WHITE);
		} }, 80);
	}


	public void displayNextGreenFrameWindow(String string) {
		this.lblNextFrameWindow .setText(string);
	}
}
